package cn.tedu.web.util;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Writer;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;

import org.apache.commons.lang3.StringUtils;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.datatype.guava.GuavaModule;
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.module.paramnames.ParameterNamesModule;

import lombok.extern.slf4j.Slf4j;

    @Slf4j
    public class JsonUtil {
        private static ObjectMapper mapper;
        private static JsonInclude.Include DEFAULT_PROPERTY_INCLUSION = JsonInclude.Include.NON_DEFAULT;
        private static boolean IS_ENABLE_INDENT_OUTPUT = false;
        private static String CSV_DEFAULT_COLUMN_SEPARATOR = ",";
        static {
            try {
                initMapper();
                configPropertyInclusion();
                configIndentOutput();
                configCommon();
            } catch (Exception e) {
                log.error("jackson config error", e);
            }
        }

        private static void initMapper() {
            mapper = new ObjectMapper();
        }

        private static void configCommon() {
            config(mapper);
        }

        private static void configPropertyInclusion() {
            mapper.setSerializationInclusion(DEFAULT_PROPERTY_INCLUSION);
        }

        private static void configIndentOutput() {
            mapper.configure(SerializationFeature.INDENT_OUTPUT, IS_ENABLE_INDENT_OUTPUT);
        }

        private static void config(ObjectMapper objectMapper) {
            objectMapper.enable(JsonGenerator.Feature.WRITE_BIGDECIMAL_AS_PLAIN);
            objectMapper.enable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT);
            objectMapper.enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY);
            objectMapper.enable(DeserializationFeature.FAIL_ON_READING_DUP_TREE_KEY);
            objectMapper.enable(DeserializationFeature.FAIL_ON_NUMBERS_FOR_ENUMS);
            objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
            objectMapper.disable(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES);
            objectMapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
            objectMapper.enable(JsonParser.Feature.ALLOW_COMMENTS);
            objectMapper.disable(JsonGenerator.Feature.ESCAPE_NON_ASCII);
            objectMapper.enable(JsonGenerator.Feature.IGNORE_UNKNOWN);
            objectMapper.enable(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES);
            objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
            objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
            objectMapper.enable(JsonParser.Feature.ALLOW_SINGLE_QUOTES);
            objectMapper.registerModule(new ParameterNamesModule());
            objectMapper.registerModule(new Jdk8Module());
            objectMapper.registerModule(new JavaTimeModule());
            objectMapper.registerModule(new GuavaModule());
        }
        public static void setSerializationInclusion(JsonInclude.Include inclusion) {
            DEFAULT_PROPERTY_INCLUSION = inclusion;
            configPropertyInclusion();
        }

        public static void setIndentOutput(boolean isEnable) {
            IS_ENABLE_INDENT_OUTPUT = isEnable;
            configIndentOutput();
        }

        public static <V> V from(URL url, Class<V> c) {
            try {
                return mapper.readValue(url, c);
            } catch (IOException e) {
                log.error("jackson from error, url: {}, type: {}", url.getPath(), c, e);
                return null;
            }
        }

        public static <V> V from(InputStream inputStream, Class<V> c) {
            try {
                return mapper.readValue(inputStream, c);
            } catch (IOException e) {
                log.error("jackson from error, type: {}", c, e);
                return null;
            }
        }

        public static <V> V from(File file, Class<V> c) {
            try {
                return mapper.readValue(file, c);
            } catch (IOException e) {
                log.error("jackson from error, file path: {}, type: {}", file.getPath(), c, e);
                return null;
            }
        }

        public static <V> V from(Object jsonObj, Class<V> c) {
            try {
                return mapper.readValue(jsonObj.toString(), c);
            } catch (IOException e) {
                log.error("jackson from error, json: {}, type: {}", jsonObj.toString(), c, e);
                return null;
            }
        }

        public static <V> V from(String json, Class<V> c) {
            try {
                return mapper.readValue(json, c);
            } catch (IOException e) {
                log.error("jackson from error, json: {}, type: {}", json, c, e);
                return null;
            }
        }

        public static <V> V from(URL url, TypeReference<V> type) {
            try {
                return mapper.readValue(url, type);
            } catch (IOException e) {
                log.error("jackson from error, url: {}, type: {}", url.getPath(), type, e);
                return null;
            }
        }

        public static <V> V from(InputStream inputStream, TypeReference<V> type) {
            try {
                return mapper.readValue(inputStream, type);
            } catch (IOException e) {
                log.error("jackson from error, type: {}", type, e);
                return null;
            }
        }

        public static <V> V from(File file, TypeReference<V> type) {
            try {
                return mapper.readValue(file, type);
            } catch (IOException e) {
                log.error("jackson from error, file path: {}, type: {}", file.getPath(), type, e);
                return null;
            }
        }

        public static <V> V from(Object jsonObj, TypeReference<V> type) {
            try {
                return mapper.readValue(jsonObj.toString(), type);
            } catch (IOException e) {
                log.error("jackson from error, json: {}, type: {}", jsonObj.toString(), type, e);
                return null;
            }
        }

        public static <V> V from(String json, TypeReference<V> type) {
            try {
                return mapper.readValue(json, type);
            } catch (IOException e) {
                log.error("jackson from error, json: {}, type: {}", json, type, e);
                return null;
            }
        }

        public static <V> String to(List<V> list) {
            try {
                return mapper.writeValueAsString(list);
            } catch (JsonProcessingException e) {
                log.error("jackson to error, obj: {}", list, e);
                return null;
            }
        }

        public static <V> String to(V v) {
            try {
                return mapper.writeValueAsString(v);
            } catch (JsonProcessingException e) {
                log.error("jackson to error, obj: {}", v, e);
                return null;
            }
        }

        public static <V> void toFile(String path, List<V> list) {
            try (Writer writer = new FileWriter(new File(path), true)) {
                mapper.writer().writeValues(writer).writeAll(list);
                writer.flush();
            } catch (Exception e) {
                log.error("jackson to file error, path: {}, list: {}", path, list, e);
            }
        }

        public static <V> void toFile(String path, V v) {
            try (Writer writer = new FileWriter(new File(path), true)) {
                mapper.writer().writeValues(writer).write(v);
                writer.flush();
            } catch (Exception e) {
                log.error("jackson to file error, path: {}, obj: {}", path, v, e);
            }
        }

        public static String getString(String json, String key) {
            if (StringUtils.isEmpty(json)) {
                return null;
            }
            try {
                JsonNode node = mapper.readTree(json);
                if (null != node) {
                    return node.get(key).toString();
                } else {
                    return null;
                }
            } catch (IOException e) {
                log.error("jackson get string error, json: {}, key: {}", json, key, e);
                return null;
            }
        }

        public static Integer getInt(String json, String key) {
            if (StringUtils.isEmpty(json)) {
                return null;
            }
            try {
                JsonNode node = mapper.readTree(json);
                if (null != node) {
                    return node.get(key).intValue();
                } else {
                    return null;
                }
            } catch (IOException e) {
                log.error("jackson get int error, json: {}, key: {}", json, key, e);
                return null;
            }
        }

        public static Long getLong(String json, String key) {
            if (StringUtils.isEmpty(json)) {
                return null;
            }
            try {
                JsonNode node = mapper.readTree(json);
                if (null != node) {
                    return node.get(key).longValue();
                } else {
                    return null;
                }
            } catch (IOException e) {
                log.error("jackson get long error, json: {}, key: {}", json, key, e);
                return null;
            }
        }

        public static Double getDouble(String json, String key) {
            if (StringUtils.isEmpty(json)) {
                return null;
            }
            try {
                JsonNode node = mapper.readTree(json);
                if (null != node) {
                    return node.get(key).doubleValue();
                } else {
                    return null;
                }
            } catch (IOException e) {
                log.error("jackson get double error, json: {}, key: {}", json, key, e);
                return null;
            }
        }

        public static BigInteger getBigInteger(String json, String key) {
            if (StringUtils.isEmpty(json)) {
                return new BigInteger(String.valueOf(0.00));
            }
            try {
                JsonNode node = mapper.readTree(json);
                if (null != node) {
                    return node.get(key).bigIntegerValue();
                } else {
                    return null;
                }
            } catch (IOException e) {
                log.error("jackson get biginteger error, json: {}, key: {}", json, key, e);
                return null;
            }
        }

        public static BigDecimal getBigDecimal(String json, String key) {
            if (StringUtils.isEmpty(json)) {
                return null;
            }
            try {
                JsonNode node = mapper.readTree(json);
                if (null != node) {
                    return node.get(key).decimalValue();
                } else {
                    return null;
                }
            } catch (IOException e) {
                log.error("jackson get bigdecimal error, json: {}, key: {}", json, key, e);
                return null;
            }
        }

        public static boolean getBoolean(String json, String key) {
            if (StringUtils.isEmpty(json)) {
                return false;
            }
            try {
                JsonNode node = mapper.readTree(json);
                if (null != node) {
                    return node.get(key).booleanValue();
                } else {
                    return false;
                }
            } catch (IOException e) {
                log.error("jackson get boolean error, json: {}, key: {}", json, key, e);
                return false;
            }
        }

        public static byte[] getByte(String json, String key) {
            if (StringUtils.isEmpty(json)) {
                return null;
            }
            try {
                JsonNode node = mapper.readTree(json);
                if (null != node) {
                    return node.get(key).binaryValue();
                } else {
                    return null;
                }
            } catch (IOException e) {
                log.error("jackson get byte error, json: {}, key: {}", json, key, e);
                return null;
            }
        }

        public static <T> ArrayList<T> getList(String json, String key) {
            if (StringUtils.isEmpty(json)) {
                return null;
            }
            String string = getString(json, key);
            return from(string, new TypeReference<ArrayList<T>>() {});
        }

        public static <T> String add(String json, String key, T value) {
            try {
                JsonNode node = mapper.readTree(json);
                add(node, key, value);
                return node.toString();
            } catch (IOException e) {
                log.error("jackson add error, json: {}, key: {}, value: {}", json, key, value, e);
                return json;
            }
        }

        private static <T> void add(JsonNode jsonNode, String key, T value) {
            if (value instanceof String) {
                ((ObjectNode) jsonNode).put(key, (String) value);
            } else if (value instanceof Short) {
                ((ObjectNode) jsonNode).put(key, (Short) value);
            } else if (value instanceof Integer) {
                ((ObjectNode) jsonNode).put(key, (Integer) value);
            } else if (value instanceof Long) {
                ((ObjectNode) jsonNode).put(key, (Long) value);
            } else if (value instanceof Float) {
                ((ObjectNode) jsonNode).put(key, (Float) value);
            } else if (value instanceof Double) {
                ((ObjectNode) jsonNode).put(key, (Double) value);
            } else if (value instanceof BigDecimal) {
                ((ObjectNode) jsonNode).put(key, (BigDecimal) value);
            } else if (value instanceof BigInteger) {
                ((ObjectNode) jsonNode).put(key, (BigInteger) value);
            } else if (value instanceof Boolean) {
                ((ObjectNode) jsonNode).put(key, (Boolean) value);
            } else if (value instanceof byte[]) {
                ((ObjectNode) jsonNode).put(key, (byte[]) value);
            } else {
                ((ObjectNode) jsonNode).put(key, to(value));
            }
        }

        public static String remove(String json, String key) {
            try {
                JsonNode node = mapper.readTree(json);
                ((ObjectNode) node).remove(key);
                return node.toString();
            } catch (IOException e) {
                log.error("jackson remove error, json: {}, key: {}", json, key, e);
                return json;
            }
        }

        public static <T> String update(String json, String key, T value) {
            try {
                JsonNode node = mapper.readTree(json);
                ((ObjectNode) node).remove(key);
                add(node, key, value);
                return node.toString();
            } catch (IOException e) {
                log.error("jackson update error, json: {}, key: {}, value: {}", json, key, value, e);
                return json;
            }
        }

        public static String format(String json) {
            try {
                JsonNode node = mapper.readTree(json);
                return mapper.writerWithDefaultPrettyPrinter().writeValueAsString(node);
            } catch (IOException e) {
                log.error("jackson format json error, json: {}", json, e);
                return json;
            }
        }

        public static boolean isJson(String json) {
            try {
                mapper.readTree(json);
                return true;
            } catch (Exception e) {
                log.error("jackson check json error, json: {}", json, e);
                return false;
            }
        }

        private static InputStream getResourceStream(String name) {
            return JsonUtil.class.getClassLoader().getResourceAsStream(name);
        }

        private static InputStreamReader getResourceReader(InputStream inputStream) {
            if (null == inputStream) {
                return null;
            }
            return new InputStreamReader(inputStream, StandardCharsets.UTF_8);
        }
    }


